import csv
import sys
import os
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import traceback # 用于打印详细错误信息

# --- parse_range remains the same ---
def parse_range(range_str):
    """解析题号范围字符串 (例如 '1-5' 或 '6') 为题号列表"""
    parts = range_str.split('-')
    try:
        if len(parts) == 1:
            num = int(parts[0].strip())
            return [num]
        elif len(parts) == 2:
            start = int(parts[0].strip())
            end = int(parts[1].strip())
            if start > end:
                 raise ValueError(f"范围起始值 {start} 大于结束值 {end}")
            return list(range(start, end + 1))
        else:
            raise ValueError("范围格式应为 '数字' 或 '数字-数字'")
    except ValueError as e:
        # 让调用者处理错误打印，这里只返回空列表表示失败
        return []


# --- get_performance_level MODIFIED to return new level names ---
def get_performance_level(score, thresholds_3):
    """
    根据得分和3个阈值 [优秀最低分, 良好最低分, 合格最低分] 判断表现等级
    """
    if not isinstance(thresholds_3, list) or len(thresholds_3) != 3:
        return "错误(阈值配置)" # 返回更具体的错误信息

    excellent_min, good_min, pass_min = thresholds_3

    try:
        score = float(score)
    except (ValueError, TypeError):
         return "错误(无效分数)" # 无效分数，无法评级

    if score >= excellent_min:
        return '优秀'
    elif score >= good_min:
        return '良好'
    elif score >= pass_min:
        return '合格'
    else:
        return '须努力'

# --- MODIFIED: process_scores now uses new level names ---
def process_scores(dynamic_config, status_update_callback=None):
    """
    处理分数，使用从GUI收集的动态配置。
    dynamic_config 包含:
    - input_csv_path
    - header_row_number (1-based)
    - student_identifier_column (for student name, e.g., "姓名")
    - output_summary_csv_path
    - output_details_csv_path (for individual student results)
    - parts (list of dicts with 'name', 'question_numbers', 'thresholds_3')
    status_update_callback: 一个函数，用于将状态消息发送回GUI
    """
    # Helper function to update status via callback or print
    def update_status(message, is_error=False):
        prefix = "错误：" if is_error else ""
        full_message = f"{prefix}{message}"
        if status_update_callback:
            status_update_callback(full_message)
        else:
            print(full_message, file=sys.stderr if is_error else sys.stdout)

    # 从配置中提取路径和名称
    input_path = dynamic_config['input_csv_path']
    output_summary_path = dynamic_config['output_summary_csv_path']
    output_details_path = dynamic_config['output_details_csv_path']
    header_row_num = dynamic_config['header_row_number']
    student_name_col_name = dynamic_config['student_identifier_column'] # e.g., "姓名"
    # MODIFIED: Changed the hardcoded student ID column name
    student_id_col_name = "班内学号" # 硬编码假设学号列名为 "班内学号"
    parts_config = dynamic_config['parts']
    # MODIFIED: Updated level names
    levels = ['优秀', '良好', '合格', '须努力']

    # 初始化数据存储结构
    summary_counts = {part['name']: {level: 0 for level in levels} for part in parts_config}
    student_details_list = [] # 用于存储每个学生的详细结果
    question_col_map = {}
    student_name_col_idx = None
    student_id_col_idx = None

    update_status(f"\n--- 开始处理文件: {input_path} ---")

    try:
        # --- 文件打开逻辑 (UTF-8 / GBK fallback) ---
        infile = None
        try:
            infile = open(input_path, mode='r', encoding='utf-8', newline='')
            update_status("使用 UTF-8 编码打开输入文件。")
        except UnicodeDecodeError:
            update_status("UTF-8 解码失败，尝试使用 GBK 编码打开。")
            try:
                 infile = open(input_path, mode='r', encoding='gbk', newline='')
            except Exception as e_gbk:
                 update_status(f"使用 GBK 编码打开也失败: {e_gbk}", is_error=True)
                 return False
        except FileNotFoundError:
             update_status(f"输入文件 {input_path} 未找到。", is_error=True)
             return False
        except IOError as e:
             update_status(f"读取输入文件 {input_path} 时发生IO错误: {e}", is_error=True)
             return False

        if infile is None:
            return False

        with infile:
            reader = csv.reader(infile)

            # --- 跳过表头前的行 ---
            update_status(f"尝试在第 {header_row_num} 行读取表头...")
            header = None
            for i in range(header_row_num):
                try:
                    current_row = next(reader)
                    if i == header_row_num - 1:
                       header = current_row
                       update_status(f"  成功读取第 {header_row_num} 行作为表头。")
                except StopIteration:
                    update_status(f"文件行数不足 {header_row_num} 行，无法读取指定的表头行。", is_error=True)
                    return False

            if header is None:
                 update_status(f"未能读取到第 {header_row_num} 行的表头数据。", is_error=True)
                 return False

            # --- 解析表头，建立映射，查找学号、姓名和题号列 ---
            update_status("正在解析表头...")
            found_student_name = False
            found_student_id = False
            found_questions = False
            for i, col_name in enumerate(header):
                col_name_stripped = col_name.strip()
                if col_name_stripped.isdigit():
                    q_num = int(col_name_stripped)
                    question_col_map.setdefault(q_num, []).append(i)
                    found_questions = True
                elif col_name_stripped == student_name_col_name:
                    if student_name_col_idx is None:
                        student_name_col_idx = i
                        update_status(f"  找到学生姓名列 '{student_name_col_name}' 在列索引 {i}")
                        found_student_name = True
                elif col_name_stripped == student_id_col_name:
                    if student_id_col_idx is None:
                        student_id_col_idx = i
                        update_status(f"  找到学生学号列 '{student_id_col_name}' 在列索引 {i}")
                        found_student_id = True

            if not found_student_name:
                update_status(f"警告：未找到学生姓名列 '{student_name_col_name}'。将使用 '未知(数据行号)' 作为姓名。", is_error=True)
            if not found_student_id:
                update_status(f"警告：未找到学生学号列 '{student_id_col_name}'。将使用 'N/A' 作为学号。", is_error=True)
            if not found_questions:
                update_status(f"警告：未找到任何有效的题号数字。可能无法计算分数。", is_error=True)

            # --- 处理学生数据 ---
            update_status("开始处理学生数据...")
            for row_index, student_row in enumerate(reader):
                current_data_row_number = row_index + 1
                if not any(cell.strip() for cell in student_row):
                    update_status(f"  信息：跳过空的数据行，行号（数据区）：{current_data_row_number}")
                    continue

                # 获取学号和姓名
                student_name = f"未知(数据行{current_data_row_number})"
                if student_name_col_idx is not None and student_name_col_idx < len(student_row):
                    student_name = student_row[student_name_col_idx].strip() or f"空姓名(数据行{current_data_row_number})"

                student_id = "N/A"
                if student_id_col_idx is not None and student_id_col_idx < len(student_row):
                    student_id = student_row[student_id_col_idx].strip() or "空学号"

                student_record = {'学号': student_id, '姓名': student_name}

                # --- 计算每个部分的得分和等级 ---
                for part in parts_config:
                    part_name = part['name']
                    question_numbers = part['question_numbers']
                    thresholds_3 = part['thresholds_3']
                    section_score = 0.0
                    valid_score_found = False
                    
                    for q_num in question_numbers:
                        if q_num in question_col_map:
                            for col_index in question_col_map[q_num]:
                                if col_index < len(student_row):
                                    score_str = student_row[col_index].strip()
                                    if score_str:
                                        try:
                                            section_score += float(score_str)
                                            valid_score_found = True
                                        except ValueError:
                                            pass # 忽略无效分数

                    # 确定等级
                    level = "无法评级"
                    if valid_score_found:
                        level = get_performance_level(section_score, thresholds_3)
                        if level.startswith("错误"):
                            update_status(f"警告：学生 '{student_name}' {part_name} 评级时出错: {level}")
                            level = "评级错误"

                    # 存储学生个人等第
                    student_record[part_name] = level

                    # 统计各等第人数
                    if level in levels:
                        summary_counts[part_name][level] += 1
                
                # 将该学生的完整记录添加到列表中
                student_details_list.append(student_record)

            update_status(f"处理完成。共处理 {len(student_details_list)} 个学生数据行。")

    except Exception as e:
        update_status(f"处理输入文件时发生未预料的错误：{e}", is_error=True)
        update_status(traceback.format_exc())
        return False

    # --- 写入摘要文件 ---
    try:
        output_dir = os.path.dirname(output_summary_path)
        if output_dir and not os.path.exists(output_dir):
             os.makedirs(output_dir, exist_ok=True)

        update_status(f"准备写入摘要文件到: {output_summary_path}")
        with open(output_summary_path, mode='w', encoding='utf-8-sig', newline='') as outfile:
            writer = csv.writer(outfile)
            writer.writerow(['部分'] + levels)
            for part_cfg in parts_config:
                 part_name = part_cfg['name']
                 row_data = [part_name] + [summary_counts[part_name].get(level, 0) for level in levels]
                 writer.writerow(row_data)
        update_status(f"统计结果摘要已成功写入到: {output_summary_path}")
    except IOError as e:
        update_status(f"无法写入摘要文件 {output_summary_path}。原因：{e}", is_error=True)
        return False

    # --- 写入学生详情文件 ---
    try:
        update_status(f"准备写入学生详情文件到: {output_details_path}")
        with open(output_details_path, mode='w', encoding='utf-8-sig', newline='') as outfile:
            # The header for details file uses '学号' for consistency
            details_header = ['学号', '姓名'] + [p['name'] for p in parts_config]
            writer = csv.DictWriter(outfile, fieldnames=details_header)
            writer.writeheader()
            writer.writerows(student_details_list)
        update_status(f"学生详情报告已成功写入到: {output_details_path}")
    except IOError as e:
        update_status(f"无法写入学生详情文件 {output_details_path}。原因：{e}", is_error=True)
        return False
    except Exception as e:
        update_status(f"写入学生详情文件时发生未知错误：{e}", is_error=True)
        update_status(traceback.format_exc())
        return False
        
    return True # 所有文件写入成功

# --- GUI Application Class ---
class QualityAnalysisApp:
    def __init__(self, master):
        self.master = master
        master.title("交互式成绩分析工具 (增强版)")
        master.geometry("650x600")

        self.analysis_parts_names = ["计算部分", "概念部分", "应用部分"]
        self.input_csv_path = tk.StringVar()
        self.header_row_number = tk.StringVar(value="2")
        self.part_configs = {}

        frame = tk.Frame(master, padx=10, pady=10)
        frame.pack(fill=tk.BOTH, expand=True)
 
        row_idx = 0
        tk.Label(frame, text="成绩文件 (.csv)(注意,请删除最后几行无关行,然后在excel中另存为csv格式):").grid(row=row_idx, column=0, sticky=tk.W, pady=2)
        tk.Entry(frame, textvariable=self.input_csv_path, width=50).grid(row=row_idx, column=1, columnspan=2, sticky=tk.EW, padx=5)
        tk.Button(frame, text="选择文件...", command=self.select_input_file).grid(row=row_idx, column=3, sticky=tk.W, padx=5)

        row_idx += 1
        tk.Label(frame, text="题号所在行号:").grid(row=row_idx, column=0, sticky=tk.W, pady=2)
        tk.Entry(frame, textvariable=self.header_row_number, width=10).grid(row=row_idx, column=1, sticky=tk.W, padx=5)

        row_idx += 1
        tk.Frame(frame, height=2, bd=1, relief=tk.SUNKEN).grid(row=row_idx, column=0, columnspan=4, sticky=tk.EW, pady=10)
        
        row_idx += 1
        tk.Label(frame, text="分析部分配置:", font=('Arial', 10, 'bold')).grid(row=row_idx, column=0, sticky=tk.W, pady=(0, 5))
        
        for i, part_name in enumerate(self.analysis_parts_names):
            row_idx += 1
            part_frame = tk.LabelFrame(frame, text=part_name, padx=5, pady=5)
            part_frame.grid(row=row_idx, column=0, columnspan=4, sticky=tk.EW, pady=3)

            range_var = tk.StringVar()
            threshold_var = tk.StringVar()
            self.part_configs[part_name] = {'range': range_var, 'thresholds': threshold_var}

            tk.Label(part_frame, text="题号区间(示例:1-5):").grid(row=0, column=0, sticky=tk.W)
            tk.Entry(part_frame, textvariable=range_var, width=15).grid(row=0, column=1, padx=5)
            # MODIFIED: Updated label text
            tk.Label(part_frame, text="阈值(优秀,良好,合格最低分)(示例: 38,34,26):").grid(row=0, column=2, sticky=tk.W, padx=10)
            tk.Entry(part_frame, textvariable=threshold_var, width=20).grid(row=0, column=3, padx=5)
            part_frame.columnconfigure(3, weight=1)

        row_idx += 1
        self.run_button = tk.Button(frame, text="运行分析", command=self.run_analysis, width=15, height=2, bg="lightblue")
        self.run_button.grid(row=row_idx, column=0, columnspan=4, pady=15)

        row_idx += 1
        tk.Label(frame, text="状态和日志:", font=('Arial', 10, 'bold')).grid(row=row_idx, column=0, sticky=tk.W, pady=(5, 0))
        
        row_idx += 1
        self.status_text = scrolledtext.ScrolledText(frame, height=10, width=70, wrap=tk.WORD, state=tk.DISABLED)
        self.status_text.grid(row=row_idx, column=0, columnspan=4, sticky=tk.NSEW, pady=5)

        frame.columnconfigure(1, weight=1)
        frame.rowconfigure(row_idx, weight=1)

    def update_status(self, message):
        self.status_text.config(state=tk.NORMAL)
        self.status_text.insert(tk.END, message + "\n")
        self.status_text.see(tk.END)
        self.status_text.config(state=tk.DISABLED)
        self.master.update_idletasks()

    def select_input_file(self):
        filepath = filedialog.askopenfilename(
            title="选择成绩 CSV 文件",
            filetypes=(("CSV 文件", "*.csv"), ("所有文件", "*.*"))
        )
        if filepath:
            self.input_csv_path.set(filepath)
            self.update_status(f"已选择输入文件: {filepath}")

    def run_analysis(self):
        self.status_text.config(state=tk.NORMAL)
        self.status_text.delete('1.0', tk.END)
        self.status_text.config(state=tk.DISABLED)
        self.update_status("开始分析...")

        input_csv = self.input_csv_path.get()
        if not input_csv or not os.path.exists(input_csv):
            messagebox.showerror("错误", f"输入文件无效或不存在: {input_csv}")
            return

        try:
            header_row = int(self.header_row_number.get())
            if header_row <= 0: raise ValueError("行号必须是正整数")
        except ValueError:
            messagebox.showerror("错误", "题号所在行号无效")
            return

        student_name_col = "姓名" # 使用硬编码的 "姓名" 作为学生标识符

        parts_config_list = []
        for part_name, controls in self.part_configs.items():
            range_str, threshold_str = controls['range'].get().strip(), controls['thresholds'].get().strip()
            if not all([range_str, threshold_str]):
                messagebox.showerror("错误", f"部分 '{part_name}' 的配置不能为空")
                return

            question_numbers = parse_range(range_str)
            if not question_numbers:
                messagebox.showerror("错误", f"部分 '{part_name}' 的题号区间格式错误: '{range_str}'")
                return

            try:
                thresholds_parts = threshold_str.split(',')
                if len(thresholds_parts) != 3: raise ValueError("需要恰好 3 个阈值")
                thresholds_3 = [float(p.strip()) for p in thresholds_parts]
            except ValueError as e:
                messagebox.showerror("错误", f"部分 '{part_name}' 的阈值格式错误: '{threshold_str}'. 错误: {e}")
                return

            parts_config_list.append({'name': part_name, 'question_numbers': question_numbers, 'thresholds_3': thresholds_3})

        base_name, _ = os.path.splitext(os.path.basename(input_csv))
        output_dir = os.path.dirname(input_csv)
        output_summary_csv_path = os.path.join(output_dir, f"{base_name}_summary.csv")
        output_details_csv_path = os.path.join(output_dir, f"{base_name}_details.csv")

        dynamic_config = {
            'input_csv_path': input_csv,
            'header_row_number': header_row,
            'student_identifier_column': student_name_col,
            'output_summary_csv_path': output_summary_csv_path,
            'output_details_csv_path': output_details_csv_path,
            'parts': parts_config_list
        }

        self.update_status("配置验证通过，开始处理分数...")
        self.run_button.config(state=tk.DISABLED)
        self.master.update()

        try:
            success = process_scores(dynamic_config, self.update_status)
            if success:
                success_message = (
                    f"分析完成！\n\n"
                    f"摘要文件已保存到:\n{output_summary_csv_path}\n\n"
                    f"学生详情文件已保存到:\n{output_details_csv_path}"
                )
                self.update_status("\n任务成功完成。")
                self.update_status(f"  - 摘要文件: {output_summary_csv_path}")
                self.update_status(f"  - 详情文件: {output_details_csv_path}")
                messagebox.showinfo("完成", success_message)
            else:
                self.update_status("\n任务处理失败。请检查上面的日志获取详细信息。")
                messagebox.showerror("失败", "分析过程中遇到错误，请查看状态日志。")
        except Exception as e:
            self.update_status(f"\n运行分析时发生未预料的顶层错误: {e}", is_error=True)
            self.update_status(traceback.format_exc())
            messagebox.showerror("严重错误", f"运行分析时发生严重错误: {e}")
        finally:
             self.run_button.config(state=tk.NORMAL)
             self.update_status("分析流程结束。")

# --- Main execution ---
if __name__ == "__main__":
    root = tk.Tk()
    app = QualityAnalysisApp(root)
    root.mainloop()
